// gmod usermessages
// XXXX id of lua usermessage
// XX or XX... id of pooled string or the whole string until it's pooled, one character strings resolve this
// so 3 bytes by default, unless your umsg string isn't pooled
// lua strings are stringzero, so + 1
// 252 actual bytes

local UMSG_BASE_LEN = 3

local PacketObject = {}

function PacketObject:Create(rp)
	local o = {}
	setmetatable(o, self)
	self.__index = self

	o.buffer = {}
	o.rp = rp

	return o
end

function PacketObject:PackEntKeyValue(ent, i, type, value)
	if type == NWTYPE_STRING && #value > 32 then // 32 char maximum for nw strings, for sanity and network usage
		value = string.sub(value, 1, 32)
	end

	local c = {type, NWSIZES[type] or (#value + 1), value}
	
	if !self.buffer[ent] then
		self.buffer[ent] = {}
	end

	self.buffer[ent][i] = c
end

function PacketObject:Pack(type, value)
	if type == NWTYPE_STRING then
		umsg.String(value)
	elseif type == NWTYPE_CHAR then
		umsg.Char(value)
	elseif type == NWTYPE_SHORT then
		umsg.Short(value)
	elseif type == NWTYPE_FLOAT || type == NWTYPE_NUMBER then
		umsg.Float(value)
	elseif type == NWTYPE_BOOLEAN then
		umsg.Bool(value)
	elseif type == NWTYPE_VECTOR then
		umsg.Vector(value)
	elseif type == NWTYPE_ANGLE then
		umsg.Angle(value)
	elseif type == NWTYPE_ENTITY then
		umsg.Entity(value)
	else
		Error("Tried to pack unknown type " .. tostring(type))
	end
end

function PacketObject:Dispatch(maxbytes)
	local sent = UMSG_BASE_LEN

	if NWDEBUG then
		print("dispatching with", maxbytes, "bytes.")
	end

	// we need to reserve 2 bytes for short, the ent
	// another 1 byte for the num of items for that ent

	// another 1 byte for the type of the first item
	// another 1 byte for the first item if it were a char

	local started = false
	
	for ent, tbl in pairs(self.buffer) do

		// if we can't fit the 5 bytes, there's no point in trying
		if (maxbytes - sent) < 5 then break end
		
		local itemstosend = {}
		local estimate = sent + 3  // 3 bytes for the short + char

		for i, t in pairs(tbl) do
			if (estimate + t[2] + 1) <= maxbytes then
				table.insert(itemstosend, i)
				estimate = estimate + t[2] + 1
			end
		end

		if #itemstosend > 0 then
			if !started then
				umsg.Start("N", self.rp)
				started = true
			end

			sent = estimate

			local eindex = ent:EntIndex()
			if ent == GetWorldEntity() then eindex = -1 end

			umsg.Short(eindex)
			umsg.Char(#itemstosend)

			if NWDEBUG then
				print("sending", ent, #itemstosend)
			end

			for _, i in ipairs(itemstosend) do
				if NWDEBUG then
					print("pack", tbl[i][1], tbl[i][3])
				end

				umsg.Char(i)

				self:Pack(tbl[i][1], tbl[i][3])

				tbl[i] = nil
			end
		end

	end

	if started then
		umsg.End()
		return sent
	else
		return 0
	end
end

function PacketObject:Size()
	local size = 0
	for ent, tbl in pairs(self.buffer) do
		size = size + table.Count(tbl)
	end

	return size
end

function UnpackUm(type, um)
	if type == NWTYPE_STRING then
		return um:ReadString()
	elseif type == NWTYPE_CHAR then
		return um:ReadChar()
	elseif type == NWTYPE_SHORT then
		return um:ReadShort()
	elseif type == NWTYPE_FLOAT || type == NWTYPE_NUMBER then
		return um:ReadFloat()
	elseif type == NWTYPE_BOOLEAN then
		return um:ReadBool()
	elseif type == NWTYPE_VECTOR then
		return um:ReadVector()
	elseif type == NWTYPE_ANGLE then
		return um:ReadAngle()
	elseif type == NWTYPE_ENTITY then
		return um:ReadEntity()
	else
		Error("Tried to unpack unknown type " .. tostring(type))
		return nil
	end
end

function ParseFromUm(um)
	local tbl = {}
	local ent = um:ReadShort()

	while ent != 0 do
		if ent == -1 then ent = 0 end

		local entity = Entity(ent)

		if IsValid(entity) || entity == GetWorldEntity() then

			local nwtable = entity._NWTable

			if !nwtable then return end

			local items = um:ReadChar()
	
			if !tbl[entity] then tbl[entity] = {} end

			for i=1, items do
				local index = um:ReadChar()

				local tbl = nwtable[index]
				local name, type, proxy = tbl[1], tbl[2], tbl[3]

				local newval = UnpackUm(type, um)

				if proxy then
					pcall(proxy, entity, name, entity[name], newval)
				end

				if NWDEBUG then
					print(entity, name, newval)
				end

				entity[name] = newval
			end

		else
			print("Invalid entity ", entity, ent)
			return
		end

		ent = um:ReadShort()
	end
end

function NewPacket(rp)
	return PacketObject:Create(rp)
end